﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using UnityEngine;
using Utility.PoolSystem;
using DataSystem;
using Utility;
using Utility.DataStructure;
using System.Linq;
using Utility.Math;
using TileGroupRef = Utility.PoolSystem.UPRef<TerrainSystem.TileGroup>;
using TileRef = Utility.PoolSystem.UPRef<TerrainSystem.Tile>;
using Newtonsoft.Json;
using System.Runtime.Serialization;

namespace TerrainSystem
{
	[JsonObject (MemberSerialization = MemberSerialization.OptIn)]
	public class TerrainData
	{
		TerrainObject terrain;

		public IEnumerable<Vector2Int> Coords => tileDict_top.Keys;
		public IEnumerable<TileRef> TileRefs => tileDict_top.Values;
		public ReadOnlyCollection<TileGroupRef> TileGroupRefs { get; }
		


		public IEnumerable<Vector2Int> WarFogCoords => foggedTiles;
		public IEnumerable<Vector2Int> GroundTileCoords => groundTiles;

		public TerrainData (TerrainObject terrain)
		{
			this.terrain = terrain;

			TileGroupRefs = tileGroups.ToReadOnly ();
		}

		public void OnEnterGame ()
		{
		}

		public void OnExitGame ()
		{
			Clear ();
		}

		#region utility
		public void Clear ()
		{
			for (int i = tileGroups.Count - 1; i >= 0; i--)
			{
				var tileGroupRef = tileGroups[i];
				RemoveTileGroup (tileGroupRef, false);
				tileGroupRef.Despawn ();
			}
			NewAddedBuildable.Clear ();
		}


		public bool IsInMap (Vector2Int coord)
		{
			int r = Main.GameSetting.MapRadius;
			return coord.x >= -r && coord.x <= r && coord.y >= -r && coord.y <= r;
		}
		#endregion
		#region buildable
		public HashSet<IBuildable> NewAddedBuildable = new HashSet<IBuildable> ();
		#endregion
		#region tilegroups
		List<TileGroupRef> tileGroups = new List<TileGroupRef> ();
		public readonly List<Vector2Int> cannotAddTileCoordList = new List<Vector2Int> ();

//		public bool UpdateCannotAddTileCoordList (Vector2Int coord, TileGroupRef tileGroupRef, bool visibleOnly = true)
//		{
//			cannotAddTileCoordList.Clear ();
//			var tileGroup = tileGroupRef.Component;
//			if (tileGroup != null)
//			{
//				foreach (var tileRef in tileGroup.Tiles)
//				{
//					var tile = tileRef.Component;
//					if (tile == null)
//					{
//						continue;
//					}
//					var tileCoord = tile.LocalCoord.LocalToWorldCoord (coord, tileGroup.Orientation);
//
//					if (!IsInMap (tileCoord))
//					{
//						cannotAddTileCoordList.Add (tileCoord);
//					}
//					else if (!IsVisible (tileCoord))
//					{
//						cannotAddTileCoordList.Add (tileCoord);
//					}
//					else if (!CanAddTile (tileCoord, tileRef, visibleOnly) && !tileGroup.CanOverlap)
//					{
//						cannotAddTileCoordList.Add (tileCoord);
//					}
//				}
//			}
//			return false;
//		}

		public bool CanAddTileGroup (Vector2Int coord, TileGroupRef tileGroupRef, bool visibleOnly = true)
		{
			//UpdateCannotAddTileCoordList (coord, tileGroupRef, visibleOnly);
			var tileGroup = tileGroupRef.Component;
			if (tileGroup != null)
			{
				//test tile overlap
				int nonOverlapCount = 0;
				foreach (var tileRef in tileGroup.Tiles)
				{
					var tile = tileRef.Component;
					if (tile == null)
					{
						continue;
					}
					var tileCoord = tile.LocalCoord.LocalToWorldCoord (coord, tileGroup.Orientation);

					//in map
					if (!IsInMap (tileCoord))
					{
						return false;
					}

					//visible
					if (!IsVisible (tileCoord))
					{
						return false;
					}

					if (CanAddTile (tileCoord, tileRef, visibleOnly))
					{
						nonOverlapCount++;
					}
					else
					{
						if (tileGroup.OverlapType == TileGroupSetting.OverlapTypes.Cannot)
						{
							return false;
						}
					}
				}

				if (nonOverlapCount > 0 || tileGroup.CanCompleteOverlap)
				{
					return true;
				}
			}
			return false;
		}

		public void AddTileGroup (Vector2Int coord, TileGroupRef tileGroupRef, bool updatePathfinder = true)
		{
			var tileGroup = tileGroupRef.Component;
			if (tileGroup)
			{
				TileGroup.SetTerrainObject (tileGroupRef, terrain, coord);
				tileGroups.Add (tileGroupRef);
				Trigger_RecordRegion (tileGroupRef);

				//be overlapped
				foreach (var tileRef in tileGroup.Tiles)
				{
//					Debug.Log ($"tileGroupCoord:{tileGroup.Coord} tileLocalCoord:{tileRef.Component.Coord} tileWorldCoord:{tileGroup.LocalToWorldCoord (tileRef.Component.Coord)}");
//					foreach (var tileCoord in tileDict_top.Keys)
//					{
//						Debug.Log ($"tileDict_top: {tileCoord}");
//					}
//					Debug.Log (GetTileGroup (tileRef.Component.Coord).Component?.Coord);

					if (GetTile (tileRef.Component.TerrainLayer, tileRef.Component.Coord) is var tempTile && tempTile)
					{
						var tempTileGroupRef = tempTile.Component.TileGroupRef;
						var tempTileGroup = tempTileGroupRef.Component;
						if (tempTileGroup)
						{
							//Debug.Log ($"RemoveTile: ");
							switch (tempTileGroup.BeOverlappedTypes)
							{
								case TileGroupSetting.BeOverlappedTypes.BeRemovedCompletely:
									foreach (var tempTileRef in tempTileGroup.Tiles)
									{
										//Debug.Log ($"RemoveTile: {tempTileRef.Component.Coord}");

										RemoveTile (tempTileRef, false);
										tempTileRef.Despawn ();
											
									}

									NewAddedBuildable.Remove (tempTileGroup);
									tileGroups.Remove (tempTileGroupRef);
									tempTileGroupRef.Despawn ();
									break;
							}
						}
					}
				}
				
				//overlap
				switch (tileGroup.OverlapType)
				{
					case TileGroupSetting.OverlapTypes.Replace:
						using (var buffer = BufferList<TileRef>.Spawn ())
						{
							foreach (var tileRef in tileGroup.Tiles)
							{
								var existTile = GetTile (tileRef.Component.TerrainLayer, tileRef.Component.Coord);
								if (existTile)
								{
									RemoveTile (existTile, false);
									existTile.Despawn ();
								}

								if (!TryAddTile (tileRef.Component.Coord, tileRef, false))
								{
									buffer.Add (tileRef);
								}
							}
							foreach (var tileRef in buffer)
							{
								tileGroup.RemoveTile (tileRef);
								tileRef.Despawn ();
							}
						}
						break;
					case TileGroupSetting.OverlapTypes.Retain:
						using (var buffer = BufferList<TileRef>.Spawn ())
						{
							foreach (var tileRef in tileGroup.Tiles)
							{
								if (!TryAddTile (tileRef.Component.Coord, tileRef, false))
								{
									buffer.Add (tileRef);
								}
							}
							foreach (var tileRef in buffer)
							{
								tileGroup.RemoveTile (tileRef);
								tileRef.Despawn ();
							}
						}
						break;
				}
			}
		}

		public bool TryAddTileGroup (Vector2Int coord, TileGroupRef tileGroupRef, bool updatePathfinder = true, bool visibleOnly = true)
		{
			if (CanAddTileGroup (coord, tileGroupRef, visibleOnly))
			{
				AddTileGroup (coord, tileGroupRef, true);
				return true;
			}
			return false;
		}

		public TileGroupRef GetTileGroup (Vector2Int coord)
		{
			var tile = GetTopTile (coord).Component;
			if (tile)
			{
				return tile.TileGroupRef;
			}
			return null;
		}

		public TileGroupRef GetTileGroup (Vector2Int coord, TileGroupSetting setting)
		{
			foreach (var tgRef in tileGroups)
			{
				var tg = tgRef.Component;
				if (tg.Coord == coord && tg.Setting == setting)
				{
					return tgRef;
				}
			}
			return null;
		}


		public void RemoveTileGroup (TileGroupRef tileGroupRef, bool updatePathfinder = true)
		{
			var tileGroup = tileGroupRef.Component;
			if (tileGroup)
			{
				NewAddedBuildable.Remove (tileGroupRef.Component);

				//remove tiles
				foreach (var tileRef in tileGroup.Tiles)
				{
					if (tileRef)
					{
						var coord = tileRef.Component.Coord;
						RemoveTile (tileRef, false);
						RefillCoord (coord);
					}
				}

				tileGroups.Remove (tileGroupRef);
				Trigger_RemoveRegion (tileGroupRef);
			}
		}

		void RefillCoord (Vector2Int coord)
		{
			var newAdded = NewAddedBuildable;
			foreach (var b in newAdded)
			{
				if (b is TileGroup tg)
				{
					if (tg.TrySpawnTile (coord, out var tileRef))
					{
						AddTile (coord, tileRef);
						break;
					}
				}
			}
		}
		#endregion
		#region tilegroup trigger 
		Dictionary<Vector2Int, HashSet<TileGroupRef>> tileGroups_triggerDict = new Dictionary<Vector2Int, HashSet<TileGroupRef>> ();

		public void Trigger_RecordRegion (TileGroupRef tileGroupRef)
		{
			var tileGroup = tileGroupRef.Component;
			if (tileGroup && tileGroup.TriggerAble)
			{
				using (var set = BufferSet<Vector2Int>.Spawn ())
				{
					Trigger_FillRegionBuffer (tileGroupRef, set);
					foreach (var coord in set)
					{
						tileGroups_triggerDict.GetOrNew (coord).Add (tileGroupRef);
					}
				}
			}
		}

		public void Trigger_RemoveRegion (TileGroupRef tileGroupRef)
		{
			var tileGroup = tileGroupRef.Component;
			if (tileGroup && tileGroup.TriggerAble)
			{
				using (var set = BufferSet<Vector2Int>.Spawn ())
				{
					Trigger_FillRegionBuffer (tileGroupRef, set);
					foreach (var coord in set)
					{
						tileGroups_triggerDict.GetOrNew (coord).Remove (tileGroupRef);
					}
				}
			}
		}

		void Trigger_FillRegionBuffer (TileGroupRef tileGroupRef, HashSet<Vector2Int> set)
		{
			var tileGroup = tileGroupRef.Component;
			if (tileGroup)
			{
				foreach (var tileRef in tileGroup.Tiles)
				{
					var tile = tileRef.Component;
					if (tile)
					{
						var coord = tile.Coord;
						set.Add (coord);
						set.Add (coord + Vector2Int.up);
						set.Add (coord + Vector2Int.down);
						set.Add (coord + Vector2Int.left);
						set.Add (coord + Vector2Int.right);
					}
				}

				foreach (var coord in set)
				{
					tileGroups_triggerDict.GetOrNew (coord).Add (tileGroupRef);
				}
			}
		}

//		public void Trigger_TestRegions ()
//		{
//			using (var set = BufferSet<TileGroupRef>.Spawn ())
//			{
//				foreach (var pair in tileGroups_triggerDict)
//				{
//					if (terrain.HasPath (TerrainObject.PathfinderTypes.Standard, pair.Key))
//					{
//						foreach (var tileGroupRef in pair.Value)
//						{
//							set.Add (tileGroupRef);
//						}
//					}
//				}
//				foreach (var tileGroupRef in set)
//				{
//					tileGroupRef.Component?.OnConnected ();
//					Trigger_RemoveRegion (tileGroupRef);
//				}
//			}
//		}
		#endregion
		#region tiles
		Dictionary<TileSetting.TerrainLayers, Vector2Int, TileRef> tileDict = new Dictionary<TileSetting.TerrainLayers, Vector2Int, TileRef> ();
		Dictionary<Vector2Int, TileRef> tileDict_top = new Dictionary<Vector2Int, TileRef> ();

		bool CanAddTile (Vector2Int coord, TileRef tileRef, bool visibleOnly)
		{
			if (tileRef)
			{
				if (!visibleOnly || IsVisible (coord))
				{
					var layer = tileRef.Component.TerrainLayer;
					if (!tileDict.TryGetValue (layer, coord, out var t) || !t)
					{
//						if (layer == TileSetting.TerrainLayers.Platform)
//						{
//							return !GetBuilding (coord);
//						}
//						else
						{
							return true;
						}
					}
				}
			}
			return false;
		}

		void AddTile (Vector2Int coord, TileRef tileRef)
		{
			if (tileRef)
			{
				var tile = tileRef.Component;
				var layer = tile.TerrainLayer;
				//tileDict.Add (layer, coord, tileRef);
				if (tileDict.TryGetValue (layer,coord, out var tempTile) && tempTile)
				{
					tempTile.Despawn ();
				}
				
				tileDict[layer, coord] = tileRef;
				
				if (tileDict_top.TryGetValue (coord, out var existTile) && existTile)
				{
					var existLayer = existTile.Component.TerrainLayer;
					if (existLayer == TileSetting.TerrainLayers.Ground && layer == TileSetting.TerrainLayers.Platform)
					{
						tileDict_top[coord] = tileRef;
					}
				}
				else
				{
					tileDict_top[coord] = tileRef;
				}
			}
		}

		bool TryAddTile (Vector2Int coord, TileRef tileRef, bool visibleOnly)
		{
			if (CanAddTile (coord, tileRef, visibleOnly))
			{
				AddTile (coord, tileRef);
				return true;
			}
			return false;
		}

		void RemoveTile (TileRef tileRef, bool removeFromTileGroup)
		{
			if (tileRef)
			{
				var tile = tileRef.Component;
				var layer = tile.TerrainLayer;
				var coord = tile.Coord;

				if (removeFromTileGroup)
				{
					tileRef.Component.TileGroupRef.Component?.RemoveTile (tileRef);
				}

				if (tileDict.TryGetValue (layer, coord, out var _tile) && _tile == tileRef)
				{
					tileDict.Remove (layer, coord);
				}

				if (tileDict_top.TryGetValue (coord, out var topTile) && topTile == tileRef)
				{
					tileDict_top.Remove (coord);
					var newTop = _GetTopTile (coord);
					if (newTop)
					{
						tileDict_top[coord] = newTop;
					}
				}
			}
		}

		TileRef _GetTopTile (Vector2Int coord)
		{
			TileRef tile = null;
			if (tileDict.TryGetValue (TileSetting.TerrainLayers.Platform, coord, out tile) && tile)
			{
				return tile;
			}
			if (tileDict.TryGetValue (TileSetting.TerrainLayers.Ground, coord, out tile) && tile)
			{
				return tile;
			}
			return null;
		}

		public TileRef GetTopTile (Vector2Int coord)
		{
			return tileDict_top.TryGetValue (coord, out var tile) ? tile : null;
		}

		public TileRef GetTile (TileSetting.TerrainLayers layer, Vector2Int coord)
		{
			return tileDict.TryGetValue (layer, coord, out var tile) ? tile : null;
		}
		#endregion
		#region visibility
		[JsonProperty]
		HashSet<Vector2Int> foggedTiles = new HashSet<Vector2Int> ();
		HashSet<Vector2Int> groundTiles = new HashSet<Vector2Int> ();
		
		public void GroundTile_Init ()
		{
			groundTiles.Clear ();

			int r = Main.GameSetting.MapRadius;
			for (int x = -r; x <= r; x++)
			{
				for (int y = -r; y <= r; y++)
				{
					groundTiles.Add (new Vector2Int (x, y));
				}
			}
		}
		public void WarFog_Init ()
		{
			foggedTiles.Clear ();

			int r = Main.GameSetting.MapRadius + 1;
			for (int x = -r; x <= r; x++)
			{
				for (int y = -r; y <= r; y++)
				{
					foggedTiles.Add (new Vector2Int (x, y));
				}
			}
		}

		public void OpenFog (Vector2Int center, int radius)
		{
			for (int x = -radius; x <= radius; x++)
			{
				for (int y = -radius; y <= radius; y++)
				{
					Vector2Int coord = new Vector2Int (center.x + x, center.y + y);
					foggedTiles.Remove (coord);
				}
			}
		}

		public void OpenFog (IEnumerable<Vector2Int> centers, int radius)
		{
			foreach (var center in centers)
			{
				OpenFog (center, radius);
			}
		}

		public void OpenFog (TileGroupRef tileGroupRef, int radius)
		{
			var tilegroup = tileGroupRef.Component;
			if (tilegroup)
			{
				foreach (var tileRef in tilegroup.Tiles)
				{
					var tile = tileRef.Component;
					OpenFog (tile.Coord, radius);
				}
			}
		}

		public bool IsVisible (Vector2Int coord)
		{
			return IsInMap (coord) && !foggedTiles.Contains (coord);
		}

		public bool IsVisible (TileGroupRef tileGroupRef, bool fullyVisible)
		{
			var tileGroup = tileGroupRef.Component;
			if (tileGroup)
			{
				foreach (var tileRef in tileGroup.Tiles)
				{
					var tile = tileRef.Component;
					if (tile)
					{
						var coord = tile.Coord;
						if (IsVisible (coord))
						{
							if (!fullyVisible)
							{
								return true;
							}
						}
						else
						{
							if (fullyVisible)
							{
								return false;
							}
						}
					}
				}
			}
			return false;
		}
		#endregion

		#region serialization
		public class SaveData
		{
			public List<TileGroup.SaveData> tilegroups = new List<TileGroup.SaveData> ();
		}

		[JsonProperty]
		SaveData saveData;

		[OnSerializing]
		void OnSerializingMethod (StreamingContext context)
		{
			saveData = new SaveData ();
			//tilegroup
			foreach (var tgRef in TileGroupRefs)
			{
				var tg = tgRef.Component;
				if (tg != null)
				{
					saveData.tilegroups.Add (new TileGroup.SaveData (tg));
				}
			}
		}

		[OnSerialized]
		void OnSerializedMethod (StreamingContext context)
		{
			saveData = null;
		}

		[OnDeserializing]
		void OnDeserializingMethod (StreamingContext context)
		{
		}

		[OnDeserialized]
		void OnDeserializedMethod (StreamingContext context)
		{
			terrain = Main.Terrain;
			Clear ();
			//tilegroup
			foreach (var tgData in saveData.tilegroups)
			{
				var tg = TileGroup.Spawn (tgData);
				AddTileGroup (tgData.coord, tg, false);
				if (tgData.isNewAdded)
				{
					NewAddedBuildable.Add (tg.Component);
				}
			}
		}
		#endregion
	}


}